package com.panopset.flywheel;

import static com.panopset.Util.log;
import static com.panopset.Util.x;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;

import com.panopset.compat.Strings;
import com.panopset.compat.RezIO;

/**
 * If a report directory has been specified in the reserved property value
 * <b>com.panopset.flywheel.reports</b>, this class will be used during script
 * execution to prepare reports that show the template structure in html format.
 *
 * @author Karl Dinwiddie
 */
public final class Report {

    /**
     * Reports home link title.
     */
    private String pReportsHomeLinkTitle;

    /**
     * Reports relative path.
     */
    private String pReportsRelativePath;

    /**
     * Set reports home link title.
     *
     * @param reportsHomeLinkTitle
     *            Reports home link title.
     */
    void setReportsHomeLinkTitle(final String reportsHomeLinkTitle) {
        pReportsHomeLinkTitle = reportsHomeLinkTitle;
    }

    /**
     * Reports href.
     */
    private String pReportsHomeLinkHref;

    /**
     * Set reports home link href.
     *
     * @param reportsHomeLinkHref
     *            Reports home link href.
     */
    void setReportsHomeLinkHref(final String reportsHomeLinkHref) {
        pReportsHomeLinkHref = reportsHomeLinkHref;
    }

    /**
     * Flywheel.
     */
    private Flywheel pFlywheel;

    /**
     * Set flywheel.
     *
     * @param flywheel
     *            Flywheel.
     */
    void setFlywheel(final Flywheel flywheel) {
        pFlywheel = flywheel;
        flywheel.setReport(this);
    }

    /**
     * Report directory.
     */
    private File pReportDirectory;

    /**
     * Set report directory.
     *
     * @param reportDirectory
     *            Report directory.
     */
    void setReportDirectory(final File reportDirectory) {
        pReportDirectory = reportDirectory;
        if (!pReportDirectory.exists() && !pReportDirectory.mkdirs()) {
            log(x("Unable to create") + " :"
                    + RezIO.getCanonicalPath(reportDirectory));
        }
    }

    /**
     * Source files.
     */
    private final List<SourceFile> sourceFiles = new ArrayList<SourceFile>();

    /**
     * Template line replacements.
     */
    private final List<String[]> templateLinkReplacements

    = new ArrayList<String[]>();

    /**
     * Constructor.
     */
    public Report() {

    }

    /**
     * Do replacements.
     *
     * @param string
     *            String to perform replacements on.
     * @return results.
     */
    private String doReplacements(final String string) {
        String s = string.replace("<", "&lt;").replace(">", "&gt;")
                + Strings.getEol();
        for (String[] r : templateLinkReplacements) {
            s = s.replace(r[0], r[1]);
        }
        return s;
    }

    /**
     * Replace tokenized file references, in lists usually.
     *
     * @param srcLine
     *            Source line.
     * @param dc
     *            Depth, so that link's back to root directory can be
     *            established.
     * @return Line with any new links.
     */
    private String replaceTokenizedFileReferences(final String srcLine,
            final String dc) {
        String rtn = srcLine;
        StringTokenizer st = new StringTokenizer(srcLine, "|");
        while (st.hasMoreTokens()) {
            String test = st.nextToken();
            for (SourceFile sf : sourceFiles) {
                String path = sf.getRelativePath();
                if (path.equals(test)) {
                    StringWriter sw = new StringWriter();
                    sw.append("<a href=\"");
                    sw.append(dc);
                    sw.append(path);
                    sw.append(".html\">");
                    sw.append(path);
                    sw.append("</a>");
                    rtn = rtn.replace(path, sw.toString());
                }
            }
        }
        return rtn.toString();
    }

    /**
     * Execute report generation.
     *
     * @throws IOException
     *             IO Exception.
     */
    public void exec() throws IOException {
        if (pReportsRelativePath != null) {
            setReportDirectory(new File(
                    RezIO.getCanonicalPath(pFlywheel.getTargetDirectory()) + "/"
                            + pReportsRelativePath));
        }
        if (pReportDirectory == null) {
            return;
        }
        for (SourceFile sf : sourceFiles) {
            String[] s = new String[2];
            s[0] = Syntax.getOpenDirective() + Syntax.getDirective() + "t "
                    + sf.getRelativePath() + Syntax.getCloseDirective();
            s[1] = Syntax.getOpenDirective() + Syntax.getDirective()
                    + "t <a href=\"" + ReservedWords.DEPTH_CHARGE
                    + sf.getRelativePath() + ".html\">" + sf.getRelativePath()
                    + "</a>" + Syntax.getCloseDirective();
            if (!templateLinkReplacements.contains(s)) {
                templateLinkReplacements.add(s);
            }
        }
        for (SourceFile sf : sourceFiles) {
            String[] s = new String[2];
            s[0] = Syntax.getOpenDirective() + Syntax.getDirective() + "l "
                    + sf.getRelativePath() + Syntax.getCloseDirective();
            s[1] = Syntax.getOpenDirective() + Syntax.getDirective()
                    + "l <a href=\"" + ReservedWords.DEPTH_CHARGE
                    + sf.getRelativePath() + ".html\">" + sf.getRelativePath()
                    + "</a>" + Syntax.getCloseDirective();
            if (!templateLinkReplacements.contains(s)) {
                templateLinkReplacements.add(s);
            }
        }
        Date date = new Date();
        for (SourceFile sf : sourceFiles) {
            List<String> sw = new ArrayList<String>();
            for (String s : sf.getSourceLines()) {
                sw.add(doReplacements(s));
            }
            String templateFilePath = RezIO.getCanonicalPath(pReportDirectory)
                    + "/com.panopset.templateReport.txt";
            File templateFile = new File(templateFilePath);
            if (!templateFile.exists()) {
                com.panopset.Util.copyLibraryResource(
                        "/com/panopset/flywheel/templateReport.txt",
                        templateFilePath);
            }
            int depth = sf.getDepth();
            StringWriter depthCharge = new StringWriter();
            for (int i = 0; i < depth; i++) {
                depthCharge.append("../");
            }

            StringWriter sw2 = new StringWriter();

            for (String s : sw) {
                sw2.append(replaceTokenizedFileReferences(s,
                        depthCharge.toString()));
            }

            Flywheel.Builder b = new Flywheel.Builder()
                    .targetDirectory(pReportDirectory)
                    .file(templateFile)
                    .map("fileName", sf.getRelativePath() + ".html")
                    .map("currentFileName", sf.getRelativePath())
                    .map("topFile", pFlywheel.get(ReservedWords.FILE))
                    .map("depthCharge", depthCharge.toString())
                    .map("last_modified",
                            com.panopset.Util.LAST_MODIFIED_FORMAT.format(date))
                    .map("content",
                            sw2.toString().replace(ReservedWords.DEPTH_CHARGE,
                                    depthCharge.toString()));
            if (Strings.isPopulated(pReportsHomeLinkHref)
                    && Strings.isPopulated(pReportsHomeLinkTitle)) {
                b.map("home", "<a href=\"" + depthCharge.toString()
                        + pReportsHomeLinkHref + "\">" + pReportsHomeLinkTitle
                        + "</a>");
            }
            b.construct().exec();
            NamedMap.reportUnusedKeys(RezIO.getCanonicalPath(pReportDirectory)
                    + "/unusedkeys.txt");
        }
    }

    /**
     * Add source file.
     *
     * @param sourceFile
     *            Source file to add.
     */
    void addSourceFile(final SourceFile sourceFile) {
        for (SourceFile sf : sourceFiles) {
            if (sourceFile.equals(sf)) {
                return;
            }
        }
        sourceFiles.add(sourceFile);
    }

    /**
     * Set reports relative path to Flywheel target directory.
     *
     * @param reportsRelativePath
     *            Path relative to Flywheel target directory.
     */
    void setReportsRelativePath(final String reportsRelativePath) {
        pReportsRelativePath = reportsRelativePath;
    }
}
